概述
实现 Android 类的动态加载,我们一般通过两个类实现 DexClassLoader
和 PathClassLoader
。
这两个类的区别在很多类似的文章里面也有介绍:
- DexClassLoader:可以加载jar、apk、dex,并且支持从 SD 卡中加载文件。
- PathClassLoader:只能加载已经安装到系统中的Apk文件,也就是/data/app目录下的apk文件。
下面我们来看一下这两个类的源码:
1 | public class DexClassLoader extends BaseDexClassLoader { |
1 | public class PathClassLoader extends BaseDexClassLoader { |
可以看到,这两个类都是继承自 BaseDexClassLoader
,不同的是构造函数接受的参数不同:
先来看一下 DexClassLoader
的参数:
- dexPath:包含 class.dex 的 apk、jar 文件路径 ,多个用文件分隔符
File.pathSeparator
分隔。 - optimizedDirectory:用来缓存优化的 dex 文件的路径,即从 apk 或 jar 文件中提取出来的 dex 文件。该路径不可以为空。
- librarySearchPath:存储 C/C++ 库文件的路径。
- parent:父类加载器,可以通过
ClassLoader.getSystemClassLoader()
或者Context.getClassLoader()
获取。
再来看一下 PathClassLoader
的参数,少了 optimizedDirectory
参数,也证实了前面说的 PathClassLoader
只能加载安装过即已经进行过 odex 优化过的文件。
这里指出一下,dex 和 apk 是可以直接加载的,因为它们都是或者内部有 dex 文件,而原始的 jar 是不行的,必须转换成 dalvik 所能识别的字节码文件,转换工具可以使用android sdk中platform-tools目录下的 dx 工具:
1 | dx --dex --output=dest.jar src.jar |
一般实现类的动态加载,有两种方法:
- 扩展ClassLoader
- 扩展当前ClassLoader的Dex Path
下面来分别介绍:
动态加载的类
我们在另外一个apk中实现下的类:
1 | public class TestDynamicLoad { |
1 | public class TestDynamicLoad2 { |
然后编译生成apk后安装并push到手机的SD卡上。
然后在另外一个apk中分别通过 DexClassLoader
和 PathClassLoader
来实现类的动态加载。
扩展ClassLoader
DexClassLoader 加载
由于需要从SD卡中读取apk文件,需要加上SD卡读取权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
。
1 | String className = "com.example.hq.myapplication.TestDynamicLoad"; |
代码执行后在手机的/data/user/0/com.example.hq.testsomething/app_dex
目录生成 app-debug.dex 文件。
下面是执行的打印结果:
1 | process id 30702 |
PathClassLoader 加载
1 | String className = "com.example.hq.myapplication.TestDynamicLoad"; |
结果:
1 | dexPath = /data/app/com.example.hq.myapplication-1/base.apk |
可见,只要把类动态加载进来,TestDynamicLoad
中实例化 TestDynamicLoad2
以及调用它的方法和正常的调用没什么区别。
扩展当前CloassLoader 的 Dex Path
在 BaseDexClassLoader
里面有下面的方法:
1 | /** |
可以实现对 DexPathList
的 dexElements
扩展。
测试代码:
1 | private void testDynamicLoad() { |
结果:
1 | dexPath = /data/app/com.example.hq.myapplication-1/base.apk |
使用 createPackageContext
通过 createPackageContext 方法创建一个对应包名的上下文来访问该包的获取Resource资源(不需要相同的sharedUserId)、共享对方的data目录下的文件,包括SharePreference, file, lib等文件,动态加载class等(需要相同的sharedUserId)。
1 | private void testLoadClass() { |
结果:
1 | Test: instance = com.example.heqiang.myapplication.TestDynamicLoad@fff4e05 |